/* 
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (c) 2006-2018 Christian Walter <cwalter@embedded-solutions.at>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef _MB_H
#define _MB_H

#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>

#ifdef __cplusplus
PR_BEGIN_EXTERN_C
#endif

#include "mbport.h"
#include "mbproto.h"

/*! \defgroup modbus Modbus
 * \code #include "mb.h" \endcode
 *
 * 本模块定义了应用程序的接口。它包含使用Modbus协议栈所需的基本函数和类型。
 * 典型的应用程序应首先调用WHT_MB_Init()。
 * 如果设备已准备好响应网络请求，则必须调用eMBEnable()来激活协议栈。
 * 在主循环中，必须定期调用WHT_MB_Poll()函数。
 * 轮询之间的时间间隔取决于配置的Modbus超时时间。如果有RTOS可用，应创建一个单独的任务，且该任务应始终调用WHT_MB_Poll()函数。
 *
 * \code
 * // 将从站地址10（即0x0A）的协议栈初始化为RTU模式
 * WHT_MB_Init(MB_RTU, 0x0A);
 * for( ;; )
 * {
 *     // 调用Modbus协议栈的主轮询循环
 *     WHT_MB_Poll(  );
 *     ...
 * }
 * \endcode
 */

/* ----------------------- Defines ------------------------------------------*/

/*! \ingroup modbus
 * \brief 使用默认的Modbus TCP端口（502）
 */
#define MB_TCP_PORT_USE_DEFAULT 0   

/* ----------------------- Type definitions ---------------------------------*/

/*! \ingroup modbus
 * \brief Modbus串行传输模式（RTU/ASCII）。
 *
 * Modbus串行通信支持两种传输模式，即ASCII模式或RTU模式。
 * RTU模式速度更快，但对硬件有更高的要求，且需要网络具有较低的抖动。
 * ASCII模式速度较慢，但在较慢的链路（例如调制解调器）上更可靠
 */
typedef enum
{
    MB_RTU,                     /*!< RTU传输模式 */
    MB_ASCII,                   /*!< ASCII传输模式 */
    MB_TCP                      /*!< TCP传输模式 */
} eMBMode;

/*! \ingroup modbus
 * \brief 寄存器应该被写入还是读取。
 *
 * 此值会传递给支持读取或写入寄存器值的回调函数。写入意味着应更新应用程序寄存器，而读取则意味着Modbus协议栈需要了解当前的寄存器值。
 *
 * \see WHT_MB_RegHolding_Callback( ), WHT_MB_RegCoils_Callback( ), WHT_MB_RegDiscrete_Callback( ) and  WHT_MB_RegInput_Callback( ).
 */
typedef enum
{
    MB_REG_READ,                /*!< 读取寄存器值并传递给协议栈 */
    MB_REG_WRITE                /*!< 更新寄存器值 */
} eMBRegisterMode;

/*! \ingroup modbus
 * \brief 协议栈中所有函数使用的错误代码
 */
typedef enum
{
    MB_ENOERR,                  /*!< 无错误 */
    MB_ENOREG,                  /*!< 非法的寄存器地址 */
    MB_EINVAL,                  /*!< 非法参数 */
    MB_EPORTERR,                /*!< 移植层错误 */
    MB_ENORES,                  /*!< 资源不足 */
    MB_EIO,                     /*!< 输入/输出错误 */
    MB_EILLSTATE,               /*!< 协议栈处于非法状态 */
    MB_ETIMEDOUT                /*!< 发生超时错误 */
} eMBErrorCode;


/* ----------------------- Function prototypes ------------------------------*/
/*! \ingroup modbus
 * \brief 初始化Modbus协议栈.
 *
 * 此函数初始化ASCII或RTU模块，并调用移植层的初始化函数以准备硬件。
 * 请注意，接收器仍处于禁用状态，在调用eMBEnable()之前，不会处理任何Modbus帧。
 *
 * \param eMode 应使用 ASCII 模式还是 RTU 模式。
 * \param ucSlaveAddress 从机地址。只有发送到此地址或广播地址的帧才会被处理
 * \param ucPort 要使用的端口。例如，在Windows系统上，1代表COM1。该值取决于平台，有些端口会直接忽略它。
 * \param ulBaudRate 波特率。例如 19200。支持的波特率取决于移植层。
 * \param eParity 用于串行传输的奇偶校验。
 *
 * \return 如果没有发生错误，该函数将返回eMBErrorCode::MB_ENOERR。
 *   此时协议处于禁用状态，可通过调用eMBEnable()进行激活。
 *   否则，将返回以下错误代码之一
 *   被返回：
 *    - eMBErrorCode::MB_EINVAL 如果从机地址无效。有效的从机地址范围是1-247。
 *    - eMBErrorCode::MB_EPORTERR 如果移植层返回了一个错误。
 */
extern eMBErrorCode WHT_MB_Init(eMBMode eMode, uint8_t Slave_Address);

/*! \ingroup modbus
 * \brief 为Modbus TCP初始化Modbus协议栈。
 *
 * 此函数用于初始化Modbus TCP模块。请注意，在调用eMBEnable()之前，帧处理仍处于禁用状态。
 *
 * \param usTCPPort 要监听的TCP端口。
 * \return 如果协议栈已正确初始化，该函数将返回eMBErrorCode::MB_ENOERR。
 * 否则，将返回以下错误代码之一：
 *    - eMBErrorCode::MB_EINVAL 如果从机地址无效。有效的从机地址范围是1-247。
 *    - eMBErrorCode::MB_EPORTERR 如果移植层返回了错误。
 */
extern eMBErrorCode WHT_MB_TCP_Init(uint16_t usTCPPort);


/*! \ingroup modbus
 * \brief Modbus协议栈的主轮询循环。
 *
 * 此函数必须定期调用。所需的定时器间隔由依赖于应用程序的Modbus从站超时时间决定。
 * 在内部，该函数会调用xMBPortEventGet()，并等待来自接收器或发送器状态机的事件。
 *
 * \return 如果协议栈未处于启用状态，则该函数返回
 *   eMBErrorCode::MB_EILLSTATE
 *   eMBErrorCode::MB_ENOERR
 */
extern eMBErrorCode WHT_MB_Poll(void);

/**
 * @brief 动态设置Modbus从站地址
 * @param ucNewAddress 新的从站地址，有效范围为1-247
 * @return 错误代码 MB_ENOERR 表示成功，MB_EINVAL 表示无效参数
 */
extern eMBErrorCode WHT_MB_Set_Slave_Address(uint8_t ucNewAddress);

/*! \ingroup modbus
 * \brief 配置设备的从机标识
 *
 * 此函数应在启用Modbus功能报告从站ID时调用（通过在mbconfig.h中定义 MB_FUNC_OTHER_REP_SLAVEID_ENABLED）。
 *
 * \param ucSlaveID 值在报告从站ID响应的从站ID字节中返回。
 * \param xIsRunning 如果为TRUE，则<>运行指示器状态<>字节设置为0xFF。否则，<>运行指示器状态<>为0x00。
 * \param pucAdditional 应在报告从站ID响应的附加字节中返回的值。
 * \param usAdditionalLen 缓冲区pucAdditonal的长度。
 *
 * \return 如果在mbconfig.h中由MB_FUNC_OTHER_REP_SLAVEID_BUF定义的静态缓冲区太小，它会返回
 * eMBErrorCode::MB_ENORES。
 * eMBErrorCode::MB_ENOERR。
 */
extern eMBErrorCode WHT_MB_Set_Slave_ID(uint8_t ucSlaveID, bool xIsRunning, uint8_t const *pucAdditional, uint16_t usAdditionalLen);

/*! \ingroup modbus
 * \brief 为给定的功能代码注册一个回调处理器。
 *
 * 此函数为给定的功能码注册一个新的回调处理程序。
 * 所提供的回调处理程序负责解析Modbus协议数据单元（PDU）并创建相应的响应。
 * 若发生错误，它应返回一种可能的Modbus异常，从而使协议栈发送一个Modbus异常帧。 
 *
 * \param ucFunctionCode 此处理程序应注册的Modbus功能码。有效的功能码范围是1到127。
 * \param pxHandler 在收到此类帧的情况下应调用的函数处理程序。如果为 NULL，则会移除此前为此函数代码注册的函数处理程序。
 *
 * \return 如果已安装处理程序，则返回eMBErrorCode::MB_ENOERR。如果没有更多资源可用，则返回eMBErrorCode::MB_ENORES。
 * 在这种情况下，应调整mbconfig.h中的值。如果参数无效，则返回eMBErrorCode::MB_EINVAL。
 */
extern eMBErrorCode WHT_MB_Register_Callback(uint8_t ucFunctionCode, WHT_MB_Function_Handler_t pxHandler);

/* ----------------------- Callback -----------------------------------------*/

/*! \defgroup modbus_registers Modbus Registers
 * \code #include "mb.h" \endcode
 * 协议栈不会在内部为寄存器分配任何内存。这使得协议栈非常小巧，也可在低端目标设备上使用。此外，这些值不必存放在内存中，例如可以存储在闪存中。
 * 每当协议栈需要某个值时，它会调用其中一个回调函数，并将寄存器地址和要读取的寄存器数量作为参数传入。
 * 然后，应用程序应读取实际的寄存器值（例如ADC电压），并将结果存储在提供的缓冲区中。
 * 如果协议栈因收到写寄存器函数而想要更新某个寄存器值，会将包含新寄存器值的缓冲区传递给回调函数。该函数随后应使用这些值来更新应用程序的寄存器值。
 */

/*! \ingroup modbus_registers
 * \brief 如果协议栈需要<输入寄存器>的值，则会使用回调函数。起始寄存器地址由 usAddress给出，最后一个寄存器由<usAddress + usNRegs - 1>给出。
 *
 * \param pucRegBuffer 一个缓冲区，回调函数应向其中写入Modbus寄存器的当前值。
 * \param usAddress 寄存器的起始地址。输入寄存器的范围是1-65535。
 * \param usNRegs 回调函数必须提供的寄存器数量。
 *
 * \return 该函数必须返回以下错误代码之一：
 *   - eMBErrorCode::MB_ENOERR：如果未发生错误。在这种情况下，会发送正常的Modbus响应。
 *   - eMBErrorCode::MB_ENOREG：如果应用程序无法提供此范围内寄存器的值。在这种情况下，会发送一个<非法数据地址>异常帧作为响应。
 *   - eMBErrorCode::MB_ETIMEDOUT：如果请求的寄存器块当前不可用，且会违反依赖于应用程序的响应超时设置。在这种情况下，会发送一个<从设备忙>异常作为响应。
 *   - eMBErrorCode::MB_EIO：如果发生了不可恢复的错误。在这种情况下，会发送一个<从设备故障>异常作为响应。
 */
extern eMBErrorCode WHT_MB_RegInput_Callback(uint8_t* pucRegBuffer, uint16_t usAddress, uint16_t usNRegs);

/*! \ingroup modbus_registers
 * \brief 如果协议栈读取或写入<保持寄存器>的值，将使用回调函数。起始寄存器地址由 usAddress给出，最后一个寄存器由<usAddress + usNRegs - 1>给出。
 *
 * \param pucRegBuffer 如果应用程序注册的值需要更新，缓冲区会指向新的寄存器值。如果协议栈需要了解当前值，回调函数应将这些值写入该缓冲区。
 * \param usAddress 寄存器的起始地址。
 * \param usNRegs 要读取或写入的寄存器数量。
 * \param eMode 如果是eMBRegisterMode::MB_REG_WRITE，应用程序寄存器的值应从缓冲区中的值更新。
 * 例如，当Modbus主机发出<写入单个寄存器>命令时，就会出现这种情况。如果是eMBRegisterMode::MB_REG_READ，应用程序应将当前值复制到缓冲区 pucRegBuffer中。
 *
 * \return 该函数必须返回以下错误代码之一：
 *   - eMBErrorCode::MB_ENOERR：如果未发生错误。在这种情况下，会发送正常的Modbus响应。
 *   - eMBErrorCode::MB_ENOREG：如果应用程序无法提供此范围内寄存器的值。在这种情况下，会发送一个<非法数据地址>异常帧作为响应。
 *   - eMBErrorCode::MB_ETIMEDOUT：如果请求的寄存器块当前不可用，且会违反依赖于应用程序的响应超时设置。在这种情况下，会发送一个<从设备忙>异常作为响应。
 *   - eMBErrorCode::MB_EIO：如果发生了不可恢复的错误。在这种情况下，会发送一个<从设备故障>异常作为响应。
 */
extern eMBErrorCode WHT_MB_RegHolding_Callback(uint8_t* pucRegBuffer, uint16_t usAddress, uint16_t usNRegs, eMBRegisterMode eMode);

/*! \ingroup modbus_registers
 * \brief 如果协议栈读取或写入<线圈寄存器>的值，将使用回调函数。如果要使用此函数，您可以使用WHT_MB_Util_Set_Bits()和WHT_MB_Util_Get_Bits()函数来处理位域。
 *
 * \param pucRegBuffer 这些位被打包在字节中
 * 其中从地址 usAddress 开始的第一个线圈存储在缓冲区 <pucRegBuffer> 第一个字节的最低有效位（LSB）中。
 * 如果缓冲区需要由回调函数写入，未使用的线圈值（即当使用的线圈数量不是8的倍数时）应设置为零。
 * \param usAddress 第一个线圈编号。
 * \param usNCoils 请求的线圈值数量。
 * \param eMode 
 * 如果是eMBRegisterMode::MB_REG_WRITE模式，应用程序的值应从缓冲区 pucRegBuffer中提供的值更新。
 * 如果是eMBRegisterMode::MB_REG_READ模式，应用程序应将当前值存储在缓冲区 pucRegBuffer中。
 *
 * \return 该函数必须返回以下错误代码之一：
 *   - eMBErrorCode::MB_ENOERR：如果未发生错误。在这种情况下，会发送正常的Modbus响应。
 *   - eMBErrorCode::MB_ENOREG：如果应用程序未在请求的地址范围内映射线圈。在这种情况下，会发送<非法数据地址>作为响应。
 *   - eMBErrorCode::MB_ETIMEDOUT：如果请求的寄存器块当前不可用，且会违反依赖于应用程序的响应超时。在这种情况下，会发送<从设备忙>异常作为响应。
 *   - eMBErrorCode::MB_EIO：如果发生不可恢复的错误。在这种情况下，会发送<从设备故障>异常作为响应。
 */
extern eMBErrorCode WHT_MB_RegCoils_Callback(uint8_t* pucRegBuffer, uint16_t usAddress, uint16_t usNCoils, eMBRegisterMode eMode);

/*! \ingroup modbus_registers
 * \brief 如果协议栈读取了<>输入离散寄存器<>的值，则会使用回调函数。
 *
 * 如果你打算使用他的函数，你可以使用WHT_MB_Util_Set_Bits()和WHT_MB_Util_Get_Bits()这两个函数来处理位域。
 *
 * \param pucRegBuffer 缓冲区应使用当前的线圈值进行更新。
 * 从 usAddress开始的第一个离散输入必须存储在缓冲区第一个字节的最低有效位（LSB）。如果请求的数量不是8的倍数，剩余的位应设为零。
 * \param usAddress 第一个离散输入的起始地址。
 * \param usNDiscrete 离散输入值的数量。
 * \return 该函数必须返回以下错误代码之一：
 *   - eMBErrorCode::MB_ENOERR：如果未发生错误。在这种情况下，会发送正常的Modbus响应。
 *   - eMBErrorCode::MB_ENOREG：如果不存在此类离散输入。在这种情况下，会发送一个<非法数据地址>异常帧作为响应。
 *   - eMBErrorCode::MB_ETIMEDOUT：如果请求的寄存器块当前不可用，且会违反与应用相关的响应超时。在这种情况下，会发送一个<从设备忙>异常作为响应。
 *   - eMBErrorCode::MB_EIO：如果发生不可恢复的错误。在这种情况下，会发送一个<从设备故障>异常作为响应。
 */
extern eMBErrorCode WHT_MB_RegDiscrete_Callback(uint8_t* pucRegBuffer, uint16_t usAddress, uint16_t usNDiscrete);

#ifdef __cplusplus
PR_END_EXTERN_C
#endif
#endif
